Review of .Net Core 3

Some key features in .Net Core 3 / C# v8

  • Nullable Reference Types
  • Pattern Matching Improvements
  • First class support for Indices and Ranges
  • Default Interface Members
  • Async Streams
  • JSON Parsing improvements
  • Windows Desktop / Winforms Support
  • Improvements to build, pack and deploy

Nullable Reference Types

The Null Reference Exception:

static void NullException(MyObject obj)
{
    Console.WriteLine(obj.title);
}

nullable reference type is noted using the same syntax as nullable value types: a ? is appended to the type of the variable. For example, the following variable declaration represents a nullable string variable, name:

string? name;

Any variable where the ? is not appended to the type name is a non-nullable reference type. That includes all reference type variables in existing code when you have enabled this feature.

The compiler uses static analysis to determine if a nullable reference is known to be non-null. The compiler warns you if you dereference a nullable reference when it may be null. You can override this behavior by using the null-forgiving operator ! following a variable name. For example, if you know the name variable isn’t null but the compiler issues a warning, you can write the following code to override the compiler’s analysis:

name!.Length;

Any reference type can have one of four nullabilities, which describes when warnings are generated:

  • Nonnullable: Null can’t be assigned to variables of this type. Variables of this type don’t need to be null-checked before dereferencing.
  • Nullable: Null can be assigned to variables of this type. Dereferencing variables of this type without first checking for null causes a warning.
  • Oblivious: This is the pre-C# 8.0 state. Variables of this type can be dereferenced or assigned without warnings.
  • Unknown: This is generally for type parameters where constraints don’t tell the compiler that the type must be nullable or nonnullable.

Pattern Matching Improvements

We can now use deconstructors to do positional and property pattern matching. The following example shows how this could be used.

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(".Net Core 3 and CSharp 8 Review");

        Teacher t1 = new Teacher("John Smith", "English");
        Teacher t2 = new Teacher("Jack Comp", "Math");
        Student s1 = new Student("Joe Snow", 7, t1);
        Student s2 = new Student("Jack Shack", 8, t2);
        Console.WriteLine(PatternMatch.IsInSeventhGradeMath(s1));
        Console.WriteLine(PatternMatch.IsInSeventhGradeMath(s2));
        Console.WriteLine(PatternMatch.IsTeacherSubjectEnglish(t2));
    }
}

static class PatternMatch
{
    public static bool IsInSeventhGradeMath(Student s)
    {
        return s is Student(_, 7, Teacher(_,"Math")); // Is student in 7th grade where teacher is Math
    }

    public static bool IsTeacherSubjectEnglish(Teacher t)
    {
        return t is { Subject: "Math" };
    }
}

class Student
{
    public string Name { get; set; }
    public int Grade { get; set; }
    public Teacher HomeTeacher { get; set; }

    public Student(string name, int grade, Teacher teacher)
    {
        this.Name = name;
        this.Grade = grade;
        this.HomeTeacher = teacher;
    }

    public void Deconstruct(out string name, out int grade, out Teacher teacher)
    {
        name = Name;
        grade = Grade;
        teacher = HomeTeacher;
    }
}

class Teacher
{
    public string Name { get; set; }
    public string Subject { get; set; }

    public Teacher(string name, string subject)
    {
        this.Name = name;
        this.Subject = subject;
    }

    public void Deconstruct(out string name, out string subject)
    {
        name = Name;
        subject = Subject;
    }
}

Indicies and Ranges

The new System.Index type can be used for indexing. You can create one from an int that counts from the beginning, or with a prefix ^ operator (C#) that counts from the end:

Index i1 = 3; // number 3 from beginning 
Index i2 = ^4; // number 4 from end 
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 
Console.WriteLine($"{a[i1]}, {a[i2]}"); // "3, 6"

There’s also the System.Range type, which consists of two Index values, one for the start and one for the end, and can be written with a x..y range expression (C#). You can then index with a Range, which produces a slice:

var slice = a[i1..i2]; // { 3, 4, 5 }

Async Streams

The IAsyncEnumerable<T> type is a new asynchronous version of IEnumerable<T>. The language lets you await foreach over IAsyncEnumerable<T> to consume their elements, and use yield return to them to produce elements.

The following example demonstrates both production and consumption of async streams. The foreach statement is async and itself uses yield return to produce an async stream for callers. This pattern (using yield return) is the recommended model for producing async streams

async IAsyncEnumerable<int> GetBigResultsAsync() { 
  await foreach (var result in GetResultsAsync()) { 
    if (result > 20) yield return result; 
  } 
}

Build Pack and Deploy

With .net core 3 we can now create single file executables. The single ‘exe’ file will contain all necessary libraries. Also, .net core 3 supports pack features such as trimming. This reduces the file size. This is done through the project file like so:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <RuntimeIdentifier>win10-x64</RuntimeIdentifier>
    <PublishSingleFile>true</PublishSingleFile>
    <PublishTrimmed>true</PublishTrimmed>
  </PropertyGroup>
</Project>

References

Whats New in .Net Core 3.0
https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0